﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using CommandPrompt;

namespace Bacon_FTP_Client
{
    /// <summary>
    /// Class that provides the comunication and transmision methods.
    /// </summary>
    public class ClientServicesST
    {
        #region Properties()
        TcpClient tcpClient = new TcpClient();
        Client server;
        #endregion

        #region Constructor()
        /// <summary>
        /// Client services single threaded for writing and listening interactions.
        /// </summary>
        /// <param name="client">Client instance for the services initialization</param>
        public ClientServicesST(Client netServer)
        {
            server = netServer;

            server.SetNetworkParameters(tcpClient);
            IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(server._hostname.ToString()), server._port);

            try
            {
                server.GetNetworkStream(serverEndPoint, tcpClient);
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show("El cliente no pudo conectarse. Verifique la direccion destino...\n" + ex.Message);
            }
        }
        #endregion
        
        #region Implementation()
        /// <summary>
        /// Client listener thread for server responses.
        /// </summary>
        /// <param name="Sender">Object Sender</param>
        private string ListenForResponses()
        {
            try
            {
                ASCIIEncoding encoder = new ASCIIEncoding();
                byte[] message = new byte[1024];
                int bytesRead;

                bytesRead =  server._netStream.Read(message, 0, 1024);

                return encoder.GetString(message, 0, bytesRead);
            }
            catch (Exception)
            {
                System.Windows.Forms.MessageBox.Show("Error en el servidor");
                return null;
            }
        }

        /// <summary>
        /// Client writer thread to send commands to the server.
        /// </summary>
        /// <param name="Sender">Object Sender</param>
        private void WriteCommands(string command)
        {
            try
            {
                ASCIIEncoding encoder = new ASCIIEncoding();
                byte[] buffer = encoder.GetBytes(command + "\r\n");
                server._netStream.Write(buffer, 0, buffer.Length);

                server._netStream.Flush();
            }
            catch (Exception)
            {
                System.Windows.Forms.MessageBox.Show("Falla de escritura");
                //throw;
            }
        }

        /// <summary>
        /// Method to establish a comunication(Write's then read's) with the server.
        /// </summary>
        /// <param name="command">Command</param>
        /// <returns>Response from the server</returns>
        public string Comunicate(string command)
        {
            if (command != null)
                WriteCommands(command);

            return ListenForResponses();
        }

        /// <summary>
        /// Method to close and dispose of the Stream and FTPClient resources.
        /// </summary>
        public void Disconect()
        {
            server._netStream.Close();
            server._netStream.Dispose();
            tcpClient.Close();
        }

        /// <summary>
        /// Method to establish a disconect with the server.
        /// </summary>
        /// <returns>Response from the server.</returns>
        public string DisconnectServer()
        {
            string response;

            response = Comunicate(Constants.quitCmd);           
            server._netStream.Close();
            server._netStream.Dispose();
            tcpClient.Close();

            return response;
        }

        /// <summary>
        /// Method for establishing a data connection. 
        /// </summary>
        /// <param name="command">Command to be sent.</param>
        /// <param name="path">Path of the file</param>
        /// <param name="server">Client server of the connection</param>
        /// <param name="connectionIP">Ip of response from the server</param>
        /// <param name="connectionPort">Port to establish the new connection</param>
        public string NewDataConnection(string command, string path, Client server, string connectionIP, int connectionPort)
        {
            try
            {
                //String that holds the response from the server.
                string response = Comunicate(command);
                //New user for the new data connection.
                Client newConnectionToServer = new Client(connectionIP, server._username, server._password, connectionPort);
                //EndPoint on which the server established a connectio for the data transmision. 
                IPEndPoint serverEndPoint2 = new IPEndPoint(IPAddress.Parse(connectionIP), connectionPort);
                //Change the status to conected.
                newConnectionToServer.SetConnectedStatus(true);
                //Set the new TcpClient for the connection.
                newConnectionToServer.SetNetworkParameters(new TcpClient());
                //Instance a new FileTransmisionServices to access the methods that allows the data transfer
                FileTransmisionServices fts = new FileTransmisionServices(command.Remove(0, 4), path, newConnectionToServer, server);

                if (command.Remove(4) == Constants.storCmd)
                {
                    //Create the new thread for the data transfer.
                    new Thread(new ParameterizedThreadStart(fts.SendFile)).Start(serverEndPoint2);
                }
                else//Create the new thread for the data transfer.
                    new Thread(new ParameterizedThreadStart(fts.ReceiveFile)).Start(serverEndPoint2);

                return response;
                
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show("Hubo un fallo de coneccion. " + ex.Message);
                return null;
                //throw;
            }            
        }

        /// <summary>
        /// Overloaded method for establishing a data connection to list. 
        /// </summary>
        /// <param name="command">Command to be sent.</param>
        /// <param name="server">Client server of the connection</param>
        /// <param name="connectionIP">Ip of response from the server</param>
        /// <param name="connectionPort">Port to establish the new connection</param>
        public string NewDataConnection(string command, Client server, string connectionIP, int connectionPort)
        {
            try
            {
                //String that holds the response from the server.
                string response = Comunicate(command);
                //New user for the new data connection.
                Client newConnectionToServer = new Client(connectionIP, server._username, server._password, connectionPort);
                //EndPoint on which the server established a connectio for the data transmision. 
                IPEndPoint serverEndPoint2 = new IPEndPoint(IPAddress.Parse(connectionIP), connectionPort);
                //Change the status to conected.
                newConnectionToServer.SetConnectedStatus(true);
                //Set the new TcpClient for the connection.
                newConnectionToServer.SetNetworkParameters(new TcpClient());
                //Instance a new FileTransmisionServices to access the methods that allows the data transfer
                FileTransmisionServices fts = new FileTransmisionServices(newConnectionToServer, server);

                return fts.InitializeListThread(serverEndPoint2);
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show("Hubo un fallo de coneccion. " + ex.Message);
                return null;
                //throw;
            }
        }

        /// <summary>
        /// Method that sends the PASV command and starts the file transmision. 
        /// </summary>
        /// <returns>Returns the server response.</returns>
        public string SendPASVCommand()
        {
            return Comunicate(Constants.pasvCmd);
        }

        /// <summary>
        /// Method that implements the procedures to send a file throug FTP
        /// </summary>
        /// <param name="fileName">File file name</param>
        /// <param name="fullPath">File full path</param>
        /// <param name="connectionParameters">PASV response from the server</param>
        /// <param name="client">Client instance</param>
        /// <returns>Returns the response from the server</returns>
        public string AttempToSendFile(string fileName, string fullPath, string connectionParameters, Client client)
        {
            string connectionIP = null;
            int connectionPort = 0;
            string[] pasv;
            string response = null;

            try
            {
                int i1, i2;

                i1 = connectionParameters.IndexOf('(') + 1;
                i2 = connectionParameters.IndexOf(')') - i1;
                pasv = connectionParameters.Substring(i1, i2).Split(',');
            }
            catch (Exception)
            {
                Disconect();
                throw new Exception("Malformed PASV response: " + connectionParameters);
            }

            if (pasv.Length < 6)
            {
                Disconect();
                throw new Exception("Malformed PASV response: " + connectionParameters);
            }

            connectionIP = String.Format("{0}.{1}.{2}.{3}", pasv[0], pasv[1], pasv[2], pasv[3]);
            connectionPort = (int.Parse(pasv[4]) << 8) + int.Parse(pasv[5]);

            try
            {
               response = NewDataConnection(Constants.storCmd + " " + fileName, fullPath, client, connectionIP, connectionPort);
            }
            catch (Exception ex)
            {
                throw new Exception("Failed to connect for data transfer: " + ex.Message);
            }

            return response;
        }

        /// <summary>
        /// Method that implements the procedures to receive a file throug FTP
        /// </summary>
        /// <param name="fileName">File file name</param>
        /// <param name="fullPath">File full path</param>
        /// <param name="connectionParameters">PASV response from the server</param>
        /// <param name="client">Client instance</param>
        /// <returns>Returns the response from the server</returns>
        public string AttempToReceiveFile(string fileName, string fullPath, string connectionParameters, Client client)
        {
            string connectionIP = null;
            int connectionPort = 0;
            string[] pasv;
            string response = null;

            try
            {
                int i1, i2;

                i1 = connectionParameters.IndexOf('(') + 1;
                i2 = connectionParameters.IndexOf(')') - i1;
                pasv = connectionParameters.Substring(i1, i2).Split(',');
            }
            catch (Exception)
            {
                Disconect();
                throw new Exception("Malformed PASV response: " + connectionParameters);
            }

            if (pasv.Length < 6)
            {
                Disconect();
                throw new Exception("Malformed PASV response: " + connectionParameters);
            }

            connectionIP = String.Format("{0}.{1}.{2}.{3}", pasv[0], pasv[1], pasv[2], pasv[3]);
            connectionPort = (int.Parse(pasv[4]) << 8) + int.Parse(pasv[5]);

            try
            {
                response = NewDataConnection(Constants.retrCmd + " " + fileName, fullPath, client, connectionIP, connectionPort);
            }
            catch (Exception ex)
            {
                throw new Exception("Failed to connect for data transfer: " + ex.Message);
            }

            return response;
        }

        /// <summary>
        /// Method that implements the procedures to list the files throug FTP
        /// </summary>
        /// <param name="command">Command from the client.</param>
        /// <param name="connectionParameters">PASV response from the server</param>
        /// <param name="client">Client instance</param>
        /// <returns>Returns the response from the server</returns>
        public string AttempToList(string command , string connectionParameters, Client client) 
        {
            string connectionIP = null;
            int connectionPort = 0;
            string[] pasv;
            string response = null;

            try
            {
                int i1, i2;

                i1 = connectionParameters.IndexOf('(') + 1;
                i2 = connectionParameters.IndexOf(')') - i1;
                pasv = connectionParameters.Substring(i1, i2).Split(',');
            }
            catch (Exception)
            {
                Disconect();
                throw new Exception("Malformed PASV response: " + connectionParameters);
            }

            if (pasv.Length < 6)
            {
                Disconect();
                throw new Exception("Malformed PASV response: " + connectionParameters);
            }

            connectionIP = String.Format("{0}.{1}.{2}.{3}", pasv[0], pasv[1], pasv[2], pasv[3]);
            connectionPort = (int.Parse(pasv[4]) << 8) + int.Parse(pasv[5]);

            try
            {
                response = NewDataConnection(Constants.listCmd, client, connectionIP, connectionPort);
            }
            catch (Exception ex)
            {
                throw new Exception("Failed to connect for data transfer: " + ex.Message);
            }

            return response;
        }

        #endregion
    }
}
